package com.justgame.project.generator.schema;

import com.justgame.project.generator.enums.FilterType;
import com.justgame.project.generator.enums.Rule;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.lang.NonNull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Getter
@SuppressWarnings("all")
public class Service {

    public static Builder builder(){
        return new Builder();
    }

    public static class Builder{
        private final List<ServiceEach> items;

        protected Builder(){
            items = new ArrayList<>();
        }

        public List<ServiceEach> build(){
            return items;
        }

        public Builder findBy(FindBy findBy){
            items.add(new ServiceEach(findBy.name,findBy.description, findBy.getFindBy(), ServiceType.QUERY_ONE));
            return this;
        }

        public Builder listBy(ListBy listBy){
            items.add(new ServiceEach(listBy.getName(),listBy.getDescription(),listBy.getListBy(), ServiceType.QUERY_LIST));
            return this;
        }

        public Builder updateBy(UpdateBy updateBy){
            items.add(new ServiceEach(updateBy.name,updateBy.description, updateBy.getUpdateBy(), ServiceType.UPDATE));
            return this;
        }

        public Builder addBy(AddBy addBy){
            items.add(new ServiceEach(addBy.name,addBy.description, addBy.getAddBy(), ServiceType.ADD));
            return this;
        }

        public Builder countBy(FindBy findBy){
            items.add(new ServiceEach(findBy.name,findBy.description, findBy.getFindBy(), ServiceType.COUNT));
            return this;
        }

        public Builder updateBy(String ...columns){
            return defaultQueryBy(FilterType.IS,ServiceType.UPDATE,Rule.notNull(),columns);
        }

        public Builder countByIs(String ...columns){
            return defaultQueryBy(FilterType.IS,ServiceType.COUNT,Rule.notNull(),columns);
        }

        public Builder findByIs(String ...columns){
            return defaultQueryBy(FilterType.IS,ServiceType.QUERY_ONE,Rule.notNull(),columns);
        }

        public Builder listByIs(String ...columns){
            return defaultQueryBy(FilterType.IS,ServiceType.QUERY_LIST,Rule.notNull(),columns);
        }

        private Builder defaultQueryBy(FilterType filterType,ServiceType serviceType,Rule rule,String ...columns){
            items.add(new ServiceEach(
                    null,
                    null,
                    Arrays.stream(columns).map(col->new By(col,filterType).rule(rule)).collect(Collectors.toList()),
                    serviceType));
            return this;
        }
    }

    @Getter
    public static class AddBy{
        private final String name;
        private final String description;
        private final List<By> addBy;

        private AddBy(String name, String description, List<By> addBy) {
            this.name = name;
            this.description = description;
            this.addBy = addBy;
        }

        public static Builder builder(){
            return new Builder();
        }

        public static class Builder{
            private String name;
            private String description;
            private final List<Param> params;

            private Builder(){
                params = new ArrayList<>();
            }

            public Builder name(String name){
                this.name = name;
                return this;
            }

            public Builder description(String description){
                this.description = description;
                return this;
            }

            public Builder params(Param... params){
                this.params.addAll(Arrays.asList(params));
                return this;
            }

            public AddBy build(){
                return new AddBy(
                        name,
                        description,
                        params.stream().map(Param::by).collect(Collectors.toList())
                );
            }
        }

        @Getter
        public static class Param{
            private final String column;
            private final List<Rule> rules = new ArrayList<>();
            private String defaultValue;
            private String initValue;
            private Class<?> initValueClass;

            private Param(String column) {
                this.column = column;
            }

            public static Param column(String column){
                return new Param(column);
            }

            public Param rule(Rule... rule){
                this.rules.addAll(Arrays.asList(rule));
                return this;
            }

            public static Param init(@NonNull String column,@NonNull String initValue){
                return new Param(column).initValue(initValue);
            }

            private Param initValue(String initValue){
                this.initValue = initValue;
                return this;
            }

            public Param defaultValue(String defaultValue){
                this.defaultValue = defaultValue;
                return this;
            }

            private By by(){
                return new By(this.column,null)
                        .defaultValue(defaultValue)
                        .rules(rules)
                        .init(initValue,initValueClass);
            }
        }
    }

    @Getter
    public static class UpdateBy{
        private final String name;
        private final String description;
        private final List<By> updateBy;

        private UpdateBy(String name,String description,List<By> updateBy) {
            this.updateBy = updateBy;
            this.name = name;
            this.description = description;
        }

        public static UpdateBy.Builder builder(){
            return new UpdateBy.Builder();
        }

        @Getter
        public static class Builder{
            private String name;
            private String description;
            private final List<By> columns;

            private Builder(){
                columns = new ArrayList<>();
            }

            public Builder description(String description){
                this.description = description;
                return this;
            }

            public Builder params(String...columns){
                Arrays.stream(columns).forEach(this::param);
                return this;
            }

            public Builder param(String column,Rule ...rules){
                if (Objects.isNull(rules) || rules.length == 0){
                    this.columns.add(new By(column,FilterType.IS).rule(Rule.notNull()));
                }else{
                    this.columns.add(new By(column,FilterType.IS).rules(Arrays.asList(rules)));
                }
                return this;
            }

            public Builder name(String name){
                this.name = name;
                return this;
            }

            public UpdateBy build(){
                return new UpdateBy(
                        this.name,
                        this.description,
                        this.columns
                );
            }
        }
    }

    @Getter
    public static class FindBy{
        private final String name;
        private final String description;
        private final List<By> findBy;

        private FindBy(String name,String description,List<By> findBy) {
            this.findBy = findBy;
            this.name = name;
            this.description = description;
        }

        public static FindBy.Builder builder(){
            return new FindBy.Builder();
        }

        @Getter
        public static class Builder{
            private String name;
            private String description;
            private final List<By> byList;

            private Builder(){
                byList = new ArrayList<>();
            }

            public Builder description(String description){
                this.description = description;
                return this;
            }

            public Builder is(String column,Rule... rules){
                byList.add(new By(column,FilterType.IS).rules(Arrays.asList(rules)));
                return this;
            }

            public Builder in(String column,Rule... rules){
                byList.add(new By(column,FilterType.IN).rules(Arrays.asList(rules)));
                return this;
            }

            public Builder isNot(String column,Rule... rules){
                byList.add(new By(column,FilterType.IS_NOT).rules(Arrays.asList(rules)));
                return this;
            }

            public Builder notIn(String column,Rule... rules){
                byList.add(new By(column,FilterType.NOT_IN).rules(Arrays.asList(rules)));
                return this;
            }

            public Builder isNull(String column,Rule... rules){
                byList.add(new By(column,FilterType.IS_NULL).rules(Arrays.asList(rules)));
                return this;
            }

            public Builder notNull(String column,Rule... rules){
                byList.add(new By(column,FilterType.NOT_NULL).rules(Arrays.asList(rules)));
                return this;
            }

            public Builder contains(String column,Rule... rules){
                byList.add(new By(column,FilterType.LIKE).rules(Arrays.asList(rules)));
                return this;
            }

            public Builder greaterThan(String column,Rule... rules){
                byList.add(new By(column,FilterType.GT).rules(Arrays.asList(rules)));
                return this;
            }

            public Builder lessThan(String column,Rule... rules){
                byList.add(new By(column,FilterType.LT).rules(Arrays.asList(rules)));
                return this;
            }

            public Builder name(String name){
                this.name = name;
                return this;
            }

            public FindBy build(){
                return new FindBy(
                        this.name,
                        this.description,
                        this.byList
                );
            }
        }
    }

    @Getter
    public static class ListBy{
        private final String name;
        private final String description;
        private final List<By> listBy;

        private ListBy(String name,String description,List<By> listBy) {
            this.listBy = listBy;
            this.name = name;
            this.description = description;
        }

        public static Builder builder(){
            return new Builder();
        }

        @Getter
        public static class Builder{
            private String name;
            private String description;
            private final List<By> byList;

            private Builder(){
                byList = new ArrayList<>();
            }

            public Builder description(String description){
                this.description = description;
                return this;
            }

            public Builder is(String column){
                byList.add(new By(column,FilterType.IS));
                return this;
            }

            public Builder in(String column){
                byList.add(new By(column,FilterType.IN));
                return this;
            }

            public Builder isNot(String column){
                byList.add(new By(column,FilterType.IS_NOT));
                return this;
            }

            public Builder notIn(String column){
                byList.add(new By(column,FilterType.NOT_IN));
                return this;
            }

            public Builder isNull(String column){
                byList.add(new By(column,FilterType.IS_NULL));
                return this;
            }

            public Builder notNull(String column){
                byList.add(new By(column,FilterType.NOT_NULL));
                return this;
            }

            public Builder contains(String column){
                byList.add(new By(column,FilterType.LIKE));
                return this;
            }

            public Builder greaterThan(String column){
                byList.add(new By(column,FilterType.GT));
                return this;
            }

            public Builder lessThan(String column){
                byList.add(new By(column,FilterType.LT));
                return this;
            }

            public Builder name(String name){
                this.name = name;
                return this;
            }

            public ListBy build(){
                return new ListBy(
                        this.name,
                        this.description,
                        this.byList
                );
            }
        }
    }

    @Getter
    public static class By{
        @Setter
        private Entity entity;
        @Setter
        private Field field;
        private final String fieldName;
        private final FilterType filterType;
        private final List<Rule> rules = new ArrayList<>();
        private String defaultValue;
        private String initValue;
        private Class<?> initValueClass;

        protected By(String fieldName, FilterType filterType) {
            this.fieldName = fieldName;
            this.filterType = filterType;
        }

        public By rules(List<Rule> rules){
            this.rules.addAll(rules);
            return this;
        }

        public By rule(Rule rule){
            this.rules.add(rule);
            return this;
        }

        public By defaultValue(String defaultValue){
            this.defaultValue = defaultValue;
            return this;
        }

        public By init(String initValue,Class<?> initClass){
            this.initValue = initValue;
            this.initValueClass = initClass;
            return this;
        }

        @Override
        public String toString() {
            return "By{" +
                    "entity=" + entity +
                    ", field=" + field +
                    ", fieldName='" + fieldName + '\'' +
                    ", filterType=" + filterType +
                    ", rules=" + rules +
                    ", defaultValue='" + defaultValue + '\'' +
                    '}';
        }
    }

    @Getter
    @Setter
    public static class ServiceEach{
        private String methodName;
        private String description;
        private List<By> items;
        private ServiceType resultType;

        private ServiceEach(String methodName,String description, List<By> items, ServiceType resultType) {
            this.methodName = methodName;
            this.description = description;
            this.items = items;
            this.resultType = resultType;
        }
    }

    @Getter
    @RequiredArgsConstructor
    public enum ServiceType{
        QUERY_ONE("findBy","getOne"),
        QUERY_LIST("listBy","list"),
        UPDATE("updateBy","updateById"),
        COUNT("countBy","count"),
        ADD("addBy","save");
        final String prefix;
        final String wrapperMethodName;
    }
}
